Feb 17 1989 Revised July 22 1989 Revised December 25 1991 Revised January 1 1992 Small Prolog User Guide & Reference Introduction. ------------- Small Prolog is a public domain implementation of the Prolog Language, with C source provided. You can do what you like with the code, save claim it as your own work. If you embed this into a commercial application I would nevertheless appreciate a free copy of your software. There are other public domain implementations around, but I dont know of one where you get the C code apart a public domain compiler called Stony Brook Prolog, but it hasnt yet been ported to the PC. The design goals were: A minimal usable implementation. Maximum portability. Educational code. Extensibility. A small object code. Embeddability. Facilitate meta-programming. Of course these goals have not been fully met! It helps to read Hogger's book "An introduction to logic programming" (Academic Press 1984) to understand the code. You could also try Maier & Warren's "Computing with logic" - Benjamin Cummings Ed. 1988 as an alternative. On the negative side: The syntax is LISP-like which has its advantages for meta-programming and small code, but I find that it is not very friendly. There is no garbage collection of any sort. It's surprising how far you can get away with this. If you want to garbage collect on the heap, then I suggest that you make a separate zone for pairs and modify get_node() so that it in fact allocates a pair but only returns the head. Then adapt the garbage collector of the source code of Xlisp (which is public domain). You could also garbage collect some of the substitution stack. The only easily available literature I know of is an article by Maurice Bruynehooge in "Implementations of Prolog" edited by John Campbell, published by John Wiley. The book is full of typos, so good luck. There are quite a few improvements to the code to be made (at this time) such as last call optimisation. The trace facilities of version 2 are no longer minimal. Not all the "standard" builtins have been included. We thought you would enjoy putting these in. Syntax ------ The syntax of the prolog is extremely simple. You can look at one of the prolog source files (*.spr) provided to get an idea or you can look at the C souce code, or you can read the following: Comments are as in the C language: /* This is a comment */ The program may be layed out with as many spaces, line_feeds and tabs as you like providing you don't chop a lexical item in two. A lexical item is an identifier or a number or a string or brackets or the vertical bar. Although strictly speaking a program is a set of clauses AND a query, we shall call a set of clauses a program. The following is a context free grammar for the syntax. Nonterminals are distinguished from terminals by putting them inside angle brackets. Comments follow the rewrite rules and are inside /* */. -> -> -> -> -> ( ) -> () -> /* alternative */ -> ( ) -> ( |) /* use the second kind of head with caution */ -> -> -> -> -> -> -> /* depends on conditional compilation */ -> -> -> /* depends on conditional compilation */ -> () /* you can space the brackets out */ -> -> _ -> _ -> -> -> -> -> () -> (|) -> -> -> -> -> - -> -> -> -> () -> (|) /* dont use this form if the predicate is builtin */ -> ->. -> /* Dont put a string quote in the sequence of characters unless it is * immediately followed by another string quote -only one is considered. * */ -> '' -> '\n' -> '\r' -> '\t' -> '\v' -> '\f' -> '\'' -> '\"' -> '\' -> '\' -> '\' -> () -> /* The last form is permitted for one goal-queries */ -> " /* END OF GRAMMAR */ Now for 4 sample queries. You don't type the "?-". ?-((writes "Hello, world")(nl)) ?-((nl)) ?-(space_left Heap Strings Dyn SUb Trail Temp) ?-((writes "What is your name?")(read X) (writes "Hello, ")(display X)) ?-(writes "This :"" is an embeded double quote") Builtins: --------- Builtins are predefined predicates that in fact call on C code. The builtins are documented in the source file prbltin.c. The file help.spr provides "on line" documentation. You could add more builtins by imitating that file, but we suggest using an extra file. You should try to be consistent with Clocksin and Mellish's builtins (see bibliography). Of course it's not as interesting trying to define predicates like "functor", "arg" and "univ". To add your own builtins you should know the following: Small Prolog uses many global variables defined in prlush.c "Arguments" is the list of arguments of the current goal and "SubstGoal" is the (substitution) environment for this. To get at the different elements of "Arguments" you can use the ARG_XXX macros in prbltin.h. All functions make frequent use of the dereference() function in prunify.c so it is important to understand this as soon as you have understood the basic data types. This function dereferences a term in an environment and updates the globals DerefNode and DerefSubst which represent the resulting derefenced object. They are globals. The following scheme gives you an idea of how a builtin might be built. Pmybuiltin() { /* local variables used below */ int result; integer i; char *s;... /* argument extaction macros */ ARG_INT(1,i) /* the first arg expected is an INT, i is set to this */ ARG_STRING(2,s); /* the second arg expected is a string */ ... result = my_function(i,s,...,&o,...);/* you define this */ bind_...(3,o); /* the third argument is bound to "o" */ if(result == MY_SUCCESS/* you define this */)return 1; if(result == MY_FAIL/* ditto */)return 0; else return(CRASH);/* force a stack dump */ } Calling a builtin with extra arguments is harmless - they are just ignored. Missing arguments or arguments of a wrong type generally force a "stack dump" so you know where the program was when it crashed. This is a print out of all pending goal lists of the ancestors of the current goal. The top-most goal list is the list of goals of the current clause. Underneath this are the goals of the clause that called that etc. From version 2 on a builtin may backtrack by making the related function return ND_SUCCESS. The repeat builtin does this. It is the builtin's responsability to update ND_builtin_next_nodeptr when it is called. That value is restored on backtracking and is the only information that is saved from one call to the next. See how the gennum predicate is implemented. Input-output: ------------- The input output facilities are modelled on Clocksin and Mellish's description. At any one time there is a current input "stream" and a current output stream. These are usually files. You can read from a string by setting the String_input_flag. See the definition of the function getachar() in prscan.c. You might want to write a builtin to do this. Similarly you might want to write on strings. All program output that can be redirected should be done through pr_string. All program input that can be redirected should be done through getachar(). These make use of Curr_outfile and Curr_infile respectively which represent the streams. The builtins "tell" "telling" and "told" are provided and work just as in Edinburgh Prolog. "see", "seeing" and "seen" work just as in Edinburgh prolog. "writes" is used to display a string without the inverted commas. "display" will output a term. "put" is used to output a character, using the ascii code. Arithmetic: ---------- The predicates that work on reals don't work on integers and vice versa. The predicates iplus, iless, isub work on integers. If you want extra arithmetic predicates just imitate the code for these. Tracing programs. ----------------- If you #undefine TRACE_CAPABILITY Small Prolog won't be able to trace, but it will run a little faster. A pair of global variable called Trace_flag and Tracing_now turn tracing on or off. The type of tracing involved is described in Clocksin and Mellish. The builtins concerned here are : trace - sets the tracing flag to 1 (on). notrace - sets the tracing flag to 0 (off). suspend_trace - decrements the trace flag resume trace - increments the trace flag. None of these have an argument. The last two are useful if you dont want to see all the gory details of the execution, in particular, concerning the predicates you don't care tracing. From version 2 on debugging is interactive in that you may during tracing choose to step over a call (you dont care what goes on inside). The best way to see this in action is to try it out. You type a letter after each tracing step indicating what you want to do next. If you type an unknown command like a question mark for example then you will be told what you can type. Here is a more detailed explanation. Command Explanation ------- ----------- Enter Move to next execution step a Abort the current execution and return to top-level. n Stop tracing but continue the execution 1 Come back to default behaviour 2 Increase the value of Trace_flag so that the rules tried may be seen. B Return to previous backtrack point. P Return to parent goal (step back, you can do this several times) s Dont trace the intermediate steps before the success or failure of current goal. U Unleash tracing : tracing unfolds without prompts, until the next solution. Reverse tracing is only possible if you have previously executed (reverse_trace_mode). This is costly in control stack space than default behaviour because it makes the stack always store all the information that non deterministic calls need to store. You may also want to use (logging "myfile") and (nologging) to record your trace session on a file. The first predicate expects the name of the file in which a log of the session is recorded. The second closes the file. The logfile is closed if you quit Small Prolog the polite way. The builtin abort aborts the execution without forcing a stack dump. To write a builtin that does force a stack dump just make it return the manifest constant CRASH. This is a display of the stack of goal lists corresponding to the current state of execution. This is generally what gets shown if a builtin is called erroneouly. Incidentally if your program overflows a stack you get asked if you want to see the stack of pending goals. This could give you a hint of where things went wrong. It could just be that your program consumes too much stack space for the currently allowed stack space. This is determined by sprolog.inf. Running Small Prolog -------------------- If you used the makefile then just type sprolog. An MSDOS executable is provided. If you have a 386 PC or better, a DOS-extended version called sprolog.32e is supplied as well. This was compiled with Delorie's GNU GCC compiler for the 386. To run this one type "go32 sprolog.32e". The program looks for a file called sprolog.inf which contains 8 numbers for the size of the heap (from which clauses are allocated) size of the string zone size of the control stack (used by the resolution mechanism) size of the substitution stack (used to record variable bindings) size of the trail (used to reset the substitution stack) size of a zone for temporary allocations (used by temp_assertz ...) size of the read buffer (used to read atoms,strings etc ) size of the print buffer (used to print anything, this should be at least 80) If the file does not exist then default values , 32000 are used for the first 6 and 1000 for the last two. The normal PC sprolog expects each number to be less than 65536, but sprolog.32e will accept huge numbers here, if your 386 has enough memory. The Atari version will accept huge values too. Then Small Prolog looks for a file called sprolog.ini which it loads. You put commonly used predicates in this file. Small Prolog looks for a file called query.ini in which it will find the first query. This can be useful if you are continually modifying and testing a program. You would put somethying like (consult myfile.pro) in the file query.ini. To load a file you can type something like (consult myfile) if the file has no extension, or (consult "myfile.pro") if it does have an extension. To leave type (quit) Dont forget to distinguish capitals from small letters. Typical beginner's errors ------------------------- The trivial mistakes that everyone makes: 1) Loading a file twice. 2) Name conflicts that arise when you consult two source files in the same workspace, make sure the predicate names are different. A call on (listing) is useful here. 3) Misspellings, especially in names of variables. 4) Using capitals where you should be using small letters and vice versa. 5) Forgetting an argument. Load the file verif.spr and call (verif), it's very helpful. 6) You can't define a fact whose predicate is the name of a builtin. 7) It's easy to forget brackets. The programme pp.c can help you find misplaced brackets (but compile it first.) Conceptual mistakes: Forgetting that Prolog is a relational language, unlike Lisp. You can't evaluate expressions like (iplus (iplus 2 3) 6) unless you write a recursive evaluation algorithm. Expecting Prolog to be too close to C: Once a variable has a value it won't change. Expecting prolog to be perfectly declarative: Using a variable before it has found a value. Simple logically correct programmes can be procedurally defective. For example, the pair of clauses: ((father Man Child) (son Child Man) ) ((son Child Man) (father Child Man) ) will be responsable for a loop if you ask (father john peter). Some people hope that a cut will solve the problem. It wont. As soon as you put input/output in your program you can say goodbye to a lot of the declarativity: You cant backtrack on a read for example. Calling Small Prolog from your application. ----------------------------------------------- This supposes you link your objects with ours. Don't use our main() of course. If you are using a PC clone you may have to recompile the files using the large memory model instead of the compact memory model (which is faster). Call init_sprolog() once and for all. Then call execute_query() with the query passed as a string whenever you want to call a query. Data types. ---------- Prtypes.h contains an overview of the data types. Prprint.c can give you an idea of how they fit in. The data types the end-user sees are: variables atoms integers reals strings lists (pairs and the empty list) characters (these were added as an afterthought) The names of variables are jetissonned. Only their order in a clause is retained. Integers have the same size as pointers. There are some types that the Prolog user never explicitly describes with an printable object. The most important of these is the CLAUSE type, which some people call data-base references. You can bind a clause to a variable (with first_clause, for example) but you wont be able to see this binding directly. However you can use body_clause to let you look at the body of the clause - that is a list. Then there is the predicate record. This is used by the listing predicate. It is used to create a linked list of pointers to those atoms that represent the builtins. You can remove reals or characters and reduce code size by commenting out the #define REAL 6 and #define CHARACTER 7 lines in prtypes.h. Suggested extensions: ---------------------- Do something about variables' names. Use #ifdefs to keep the older version, because it's fast. You will consume more memory. Put in a rich set of numerical builtins. Improve the IO. You could snarf some code from Microemacs 3.9 for example. Improve the syntax, either in C or by a layer in Prolog. Improve the trace facilities. Add a garbage collector -you can reclaim clause space or stack space. Improve the clause indexing. Make the unification non-recursive. Suggested Projects: -------------------- Integrate this with CLIPS (available from the Austin Code Works). Integrate this with an expert system shell like Nexpert. Integrate this with Micro-Emacs (I am thinking of using text segments as data). Integrate this with Hypertext or HyperCard. Integrate this with a spreadsheet. If you add optimisations please tell me about them. The company that used to sell Fprolog has gone bust. Bugs: ---- Known uncorrected problems: Input is not very robust, incorrect syntax can lead to a core dump on Unix. A few trailing blanks at the end of the Prolog file can lead to a parse error message. clean_temp may cause problems. Small Prolog makes you think there are more solutions to a query when there arent any. It has to actually look hard to find this out. When reverse execution mode is on it looks as if all clauses are nondeterministic even if they are not. Feedback: -------- Please send feedback via the C User's Journal. Bibliography: ------------- W.F.Clocksin & C.S.Mellish "Programming in Prolog" Third Edition Springer Verlag 1987 F Klusniak & S. Spakowicz "Prolog programming for Programmers" 2nd edition Academic Press. Also discusses implementation, comes with Pascal sources of a structure-copying interpreter. I Bratko "Prolog Programming for Artificial Intelligence" Addisson Wesley 1986 (This is an excellent book that can serve as an alternative to Clocksin an Mellish's) C Hogger "An Introduction to Logic Programming" Academic Press 1985 (implementation verification and synthesis, an advanced book) David Maier David S Warren "Computing with Logic" Benjamin Cummings 1987 (implementation) NC Rowe "Artificial Intelligence through Prolog" Prentice Hall 1987 Lots of examples. P Boizumault "Prolog, l'implantation." Masson (Paris) 1987 In depth study of implementation in Lisp, covers unusual aspects like freeze, diff and optimizations. J.A. Campbell "Implementations of Prolog" Ellis-Horwood/ J Wiley 1984 Advanced topics.